// 判断当前是否为当前为commonjs环境
const isCommonjsEnvironment = () => typeof module !== "undefined" && typeof exports !== "undefined" && typeof require !== "undefined";

const getProcessVModelIdentifier = (() => {
  const fileMapName = "@@PLAIN_DESIGN_COMPOSITION_VMODEL_PROCESSOR";
  const importName = "processVModel";

  return (types, path, file) => {
    if (isCommonjsEnvironment()) {
      // 当前为commonjs环境
      let identifier = file.get(fileMapName);
      if (!identifier) {
        const programPath = path.find(p => p.isProgram());
        const scope = programPath.scope;
        identifier = scope.generateUidIdentifier(importName);
        file.set(fileMapName, identifier);
        const specifiers = [types.importSpecifier(identifier, types.identifier(importName))];
        let statement = types.importDeclaration(specifiers, types.stringLiteral('plain-design-composition'));
        programPath.unshiftContainer("body", [statement]);
      }
      return { ...identifier };
    } else {
      if (typeof window !== "undefined") {
        // 浏览器下使用的时候，调用 window.PlainDesignComposition.processVModel
        return types.memberExpression(
          types.memberExpression(
            types.identifier('window'),
            types.identifier('PlainDesignComposition'),
          ),
          types.identifier(importName)
        );
      } else {
        throw new Error('babel-plugin-react-model:无法识别当前环境,请确保在Node环境或者浏览器环境下运行！');
      }
    }
  };
})();

function genAssignmentCode(types, vModelValue, handlerParameter, path, file) {
  const object = vModelValue.object;
  const property = vModelValue.property;
  let propertyName = vModelValue.computed ? property : types.StringLiteral(property.name || property.value);
  return types.CallExpression(
    getProcessVModelIdentifier(types, path, file),
    [object, propertyName, handlerParameter]
  );
}

/**
 * v-change 适用于 ant-design 之类的组件；会编译成语法糖 value 以及 onChange；
 * 如果即使用了 v-change绑定某个属性，也显示地传递了onChange属性。那么 v-change的onChange与显示地传递的onChange会合并为一个函数。
 * 并且 v-change的onChange先执行；但是如果是在结构中传递的onChange，会导致某个onChange失效。如果v-model在结构对象之后，那么结构对象的onChange将无效；
 * 反之亦然。
 *
 * v-model 为专门用于 plain-design的组件。会编译成语法糖 value 以及 onUpdateModelValue。
 * 因为 plain-design的组件在派发时间 onUpdateModelValue的同时，也会派发onChange事件，所以没有v-change的问题。也不需要合并onChange属性。
 * 同时v-model支持绑定多个属性。比如 v-model-start 会编译成 start与 onUpdateStart，v-model-end 会编译成 end与onUpdateEnd
 *
 * @author  韦胜健
 * @date    2021/3/9 15:09
 */
const JSXModel = function (babel) {
  const { types } = babel;
  return {
    visitor: {
      Property: (path, file) => {
        const keyIdentifier = path.get('key');
        if (typeof keyIdentifier.node.value !== "string") {
          return;
        }
        if (keyIdentifier.node.value !== 'v-change' && keyIdentifier.node.value.indexOf('v-model') === -1) {
          return;
        }
        if (keyIdentifier.node.value.indexOf('v-model') === 0) {
          /*v-model 属性表达式*/
          const sugarValueExpression = path.node.value;
          if (!('property' in sugarValueExpression)) {throw new Error('v-model value must be a valid JavaScript member expression.');}
          /*propName 绑定的字段名*/
          let propName = keyIdentifier.node.value.slice(8);
          if (!propName) {propName = 'modelValue';}
          /*语法糖value属性值*/
          const sugarValue = types.objectProperty(types.identifier(propName), sugarValueExpression);

          /*语法糖change handler属性函数的第一个参数*/
          const handlerFirstParameter = types.Identifier("$event");
          /*语法糖change handler属性名*/
          const handlerPropName = propName === 'modelValue' ? 'onUpdateModelValue' : 'onUpdate' + propName.charAt(0).toUpperCase() + propName.slice(1);
          /*语法糖onChange属性值*/
          const sugarOnChange = types.objectProperty(
            types.identifier(handlerPropName),
            types.ArrowFunctionExpression(
              [handlerFirstParameter],
              genAssignmentCode(types, sugarValueExpression, handlerFirstParameter, path, file)
            )
          );
          path.replaceWithMultiple([...[
            sugarValue,
            sugarOnChange
          ]]);
        }
        if (keyIdentifier.node.value.indexOf('v-change') === 0) {
          /*v-model 属性表达式*/
          const sugarValueExpression = path.node.value;
          if (!('property' in sugarValueExpression)) {throw new Error('v-change value must be a valid JavaScript member expression.');}
          /*propName 绑定的字段名*/
          let propName = 'value';
          /*语法糖value属性值*/
          const sugarValue = types.objectProperty(types.identifier(propName), sugarValueExpression);

          /*语法糖change handler属性函数的第一个参数*/
          const handlerFirstParameter = types.Identifier("$event");
          /*语法糖change handler属性名*/
          const handlerPropName = 'onChange';
          /*语法糖onChange属性值*/
          const sugarOnChange = types.objectProperty(
            types.identifier(handlerPropName),
            types.ArrowFunctionExpression(
              [handlerFirstParameter],
              genAssignmentCode(types, sugarValueExpression, handlerFirstParameter, path, file)
            )
          );
          path.replaceWithMultiple([...[
            sugarValue,
            sugarOnChange
          ]]);
        }
      },
    }
  };
};

if (typeof window !== "undefined") {
  if ('Babel' in window) {
    Babel.registerPlugin("JSXModel", JSXModel);
  }
  window.JSXModel = JSXModel;
}

if (isCommonjsEnvironment()) {
  module.exports = JSXModel;
}
